package com.tangsm.spring.boot.csv;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
import java.util.Vector;

/**
 * SSH远程调用
 * <p>exec用于执行命令</p>
 * <p>sftp用于文件处理</p>
 *
 * @author tangyb
 * @see <a href="http://www.jcraft.com/jsch/">Jsch 官网</a>
 * @see <a href="https://www.cnblogs.com/longyg/archive/2012/06/25/2556576.html">JSch - Java实现的SFTP</a>
 */
public class SSHRemoteCall {
    private static final Logger log = LoggerFactory.getLogger(SSHRemoteCall.class);

    /**
     * sftp-SSH文件传输协议渠道
     */
    private static final String CHANNEL_SFTP = "sftp";

    /**
     * exec-执行命令渠道
     */
    private static final String CHANNEL_EXEC = "exec";

    // 私有的对象
    private static SSHRemoteCall sshRemoteCall;

    /**
     * 私有的构造方法
     */
    private SSHRemoteCall() {
    }

    /**
     * 懒汉式,线程不安全,适合单线程
     *
     * @return SSH远程调用实例
     */
    public static SSHRemoteCall getInstance() {
        if (sshRemoteCall == null) {
            sshRemoteCall = new SSHRemoteCall();
        }
        return sshRemoteCall;
    }

    /**
     * 懒汉式,线程安全,适合多线程
     *
     * @return SSH远程调用实例
     */
    public static synchronized SSHRemoteCall getInstance2() {
        if (sshRemoteCall == null) {
            sshRemoteCall = new SSHRemoteCall();
        }
        return sshRemoteCall;
    }

    /**
     * 端口号
     */
    private static int port = 22;

    /**
     * ip地址
     */
    private static String host = "175.178.168.199";
    /**
     * 账号
     */
    private static String userName = "root";
    /**
     * 密码
     */
    private static String password = "tt@850828";

    /**
     * JSCH session
     */
    private Session session;
    /**
     * 是否登陆
     */
    private boolean login = false;

    /**
     * 构造方法
     *
     * @param host     端口
     * @param userName 登录用户名称
     * @param password 登录用户密码
     */
    public SSHRemoteCall(String host, String userName, String password) {
        this(host, port, userName, password);
    }

    /**
     * 构造方法,方便直接传入host,userName,password进行调用
     *
     * @param host     服务器地址
     * @param port     端口
     * @param userName 登录用户名称
     * @param password 登录用户密码
     */
    public SSHRemoteCall(String host, int port, String userName, String password) {
        super();
        SSHRemoteCall.host = host;
        SSHRemoteCall.userName = userName;
        SSHRemoteCall.password = password;
        SSHRemoteCall.port = port;
    }

    /**
     * 远程登陆
     *
     * @param host     服务器地址
     * @param userName 登录用户名称
     * @param password 登录用户密码
     * @throws Exception 异常信息
     */
    public void sshRemoteCallLogin(String host, String userName, String password) throws Exception {
        // 如果登陆就直接返回
        if (login) {
            return;
        }
        // 创建jSch对象
        JSch jSch = new JSch();
        try {
            // 获取到jSch的session, 根据用户名、主机ip、端口号获取一个Session对象
            session = jSch.getSession(userName, host, port);
            // 设置密码
            session.setPassword(password);

            // 方式一,通过Session建立连接
            // session.setConfig("StrictHostKeyChecking", "no");
            // session.connect();

            // 方式二,通过Session建立连接
            // java.util.Properties;
            Properties config = new Properties();
            // 是否开启严格的主机密钥检查（no-否）
            config.put("StrictHostKeyChecking", "no");
            // 为Session对象设置properties
            session.setConfig(config);
            // 设置超时
            // session.setTimeout(3000);
            // 通过Session建立连接
            session.connect();

            // 设置登陆状态
            login = true;
        } catch (JSchException e) {
            // 设置登陆状态为false
            login = false;
            throw new Exception("主机登录失败, IP = " + host + ", USERNAME = " + userName + ", Exception:" + e.getMessage());
        }
    }

    /**
     * 关闭连接
     */
    public void closeSession() {
        // 调用session的关闭连接的方法
        if (session != null) {
            // 如果session不为空,调用session的关闭连接的方法
            session.disconnect();
        }

    }

    /**
     * 执行相关的命令
     *
     * @param command 命令
     * @throws IOException IO异常
     */
    public void execCommand(String command) throws IOException {
        // 输入流(读)
        InputStream in = null;
        // 定义channel变量
        Channel channel = null;
        try {
            // 如果命令command不等于null
            if (command != null) {
                // 打开channel
                //说明：exec用于执行命令;sftp用于文件处理
                channel = session.openChannel(CHANNEL_EXEC);
                // 设置command
                ((ChannelExec) channel).setCommand(command);
                // channel进行连接
                channel.connect();
                // 获取到输入流
                in = channel.getInputStream();
                // 执行相关的命令
                String processDataStream = processDataStream(in);
                log.info("命令:{}", processDataStream);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                in.close();
            }
            if (channel != null) {
                channel.disconnect();
            }
        }

    }

    /**
     * 对将要执行的linux的命令进行遍历
     *
     * @param in 字节输入流
     * @return linux的命令
     * @throws IOException IO异常信息
     */
    public String processDataStream(InputStream in) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
            String result;
            while ((result = br.readLine()) != null) {
                sb.append(result);
            }
        } catch (IOException e) {
            throw new IOException("获取数据流失败：" + e.getMessage(), e);
        }
        return sb.toString();
    }

    /**
     * 上传文件
     *
     * @param directory  上传文件的目录
     * @param uploadFile 将要上传的文件
     */
    public void uploadFile(String directory, String uploadFile) {
        try {
            // 打开channelSftp
            ChannelSftp channelSftp = (ChannelSftp) session.openChannel(CHANNEL_SFTP);
            // 远程连接
            channelSftp.connect();
            // 创建一个文件名称问uploadFile的文件
            File file = new File(uploadFile);
            // 将文件进行上传(sftp协议)
            // 将本地文件名为src的文件上传到目标服务器,目标文件名为dst,若dst为目录,则目标文件名将与src文件名相同.
            // 采用默认的传输模式:OVERWRITE
            channelSftp.put(new FileInputStream(file), directory, ChannelSftp.OVERWRITE);
            // 切断远程连接
            channelSftp.exit();
        } catch (JSchException | SftpException | FileNotFoundException e) {
            log.error("上传文件异常，", e);
        }
    }

    /**
     * 下载文件 采用默认的传输模式：OVERWRITE
     *
     * @param src linux服务器文件地址
     * @param dst 本地存放地址
     * @throws JSchException JSch异常
     * @throws SftpException sftp异常
     */
    public void fileDownload(String src, String dst) throws JSchException, SftpException {
        // src 是linux服务器文件地址,dst 本地存放地址

        ChannelSftp channelSftp = (ChannelSftp) session.openChannel(CHANNEL_SFTP);
        // 远程连接
        channelSftp.connect();
        // 下载文件,多个重载方法
        channelSftp.get(src, dst);
        // 切断远程连接,quit()等同于exit(),都是调用disconnect()
        channelSftp.quit();
        // channelSftp.disconnect();
    }

    /**
     * 删除文件
     *
     * @param path 要删除的文件路径
     * @throws SftpException sftp异常
     * @throws JSchException JSch异常
     */
    public void deleteFile(String path) throws SftpException, JSchException {
        // 打开openChannel的sftp
        ChannelSftp channelSftp = (ChannelSftp) session.openChannel(CHANNEL_SFTP);
        // 远程连接
        channelSftp.connect();
        // 删除文件
        channelSftp.rm(path);
        // 切断远程连接
        channelSftp.exit();
    }

    /**
     * 列出目录下的文件
     *
     * @param path 要列出的路径
     * @return 列出远程path指定的文件
     * @throws SftpException sftp异常
     * @throws JSchException Jsch异常
     */
    public Vector<?> listFiles(String path) throws JSchException, SftpException {
        ChannelSftp channelSftp = (ChannelSftp) session.openChannel(CHANNEL_SFTP);
        // 远程连接
        channelSftp.connect();
        // 显示目录信息
        Vector<?> ls = channelSftp.ls(path);
        // 切断连接
        channelSftp.exit();
        return ls;
    }

    public static void main(String[] args) {
        SSHRemoteCall remoteCall = null;
        // 连接到指定的服务器
        try {
            // 获取SSH远程调用实例
            remoteCall = SSHRemoteCall.getInstance();

            // 1、首先远程连接ssh
            remoteCall.sshRemoteCallLogin(host, userName, password);
            // 打印信息
            log.info("1、连接sftp服务器,ip地址: " + host + ",账号: " + userName + ",连接成功.....");

            // 2、执行相关的命令
            // 查看目录信息
            // String command = "ls /home/hadoop/package ";
            // 查看文件信息
            // String command = "cat /home/hadoop/package/test ";
            // 查看磁盘空间大小
            // String command = "df -lh ";
            // 查看cpu的使用情况
            // String command = "top -bn 1 -i -c ";
            // 查看内存的使用情况
            String command = "free ";
            remoteCall.execCommand(command);
            log.info("执行相关的命令，{}", command);

            // 3、上传文件
            String directory = "/home/hadoop/package/poi.xlsx";// 目标文件名
            String uploadFile = "D:\\poi.xlsx";// 本地文件名
            remoteCall.uploadFile(directory, uploadFile);
            log.info("2、文件上传，{}", uploadFile);

            // 4、下载文件
            // src 是linux服务器文件地址,dst 本地存放地址,采用默认的传输模式：OVERWRITE
            //test为文件名称哈
            String src = "/home/hadoop/package/test";
            String dst = "D:\\";
            remoteCall.fileDownload(src, dst);
            log.info("4、下载文件，{}", src);

            // 5、刪除文件
            String deleteDirectoryFile = "/home/hadoop/package/test";
            remoteCall.deleteFile(deleteDirectoryFile);
            log.info("5、删除的文件：{}", deleteDirectoryFile);

            // 6、展示目录下的文件信息
            String lsDirectory = "/home/hadoop/package";
            Vector<?> listFiles = remoteCall.listFiles(lsDirectory);
            log.info("5、展示目录下的文件信息，{}," + lsDirectory);
            listFiles.forEach(o -> {
                log.info(o.toString());
            });
        } catch (Exception e) {
            // 打印错误信息
            System.err.println("远程连接失败......");
            e.printStackTrace();
        } finally {
            // 7、关闭连接
            if (remoteCall != null) {
                remoteCall.closeSession();
            }
        }
    }

}
